<?php
/**
* @Charset: UTF8
*/
class query extends pdo
{
	//Константы версии
	const VERSION    = "2.4.3";
	const PHPVERSION = "5.4";
	const AUTHOR     = "Aleksey(celebron)Shatalin";
	const FRAMEWORK  = "CelebronWork";

	private $_prefix;
	public $_error_log=true; //Включить логирование ошибок
	public $_query_log=true; //Включить логирование запросов
    public $_error_log_file = './error.log'; //Файл где будет храниться текст ошибок. Работает в паре с $_error_log (Если включен). NULL - не записывать в файл
	
	//Статические переменные
	public  static $query;
	public  static $error;

	public function __construct()
	{
		if(is_array(func_get_arg(0)) && (func_num_args()==1)) {
			$config = func_get_arg(0);
		} elseif(func_num_args() > 1) {
			$config['db_dsn']       = func_get_arg(0);
			$config['db_login']     = func_get_arg(1);
			$config['db_password']  = func_get_arg(2);
			$config['db_prefix']    = func_get_arg(3);
			$config['db_error_log'] = func_get_arg(4);
			$config['db_query_log'] = func_get_arg(5);
            $config['db_error_log_file'] = func_get_arg(6);
		}

		//Если нет DSN параметра, то по умолчанию используется подключение для mysql
		if(isset($config['db_mysql_host'],$config['db_mysql_name'])) {
			$config['db_dsn'] = "mysql:hostname={$config['db_mysql_host']};dbname={$config['db_mysql_name']}";		
		}

		$this->_prefix=$config['db_prefix']; //Устанавливаем стандартный префикс
		parent::__construct($config['db_dsn'],$config['db_login'],$config['db_password']);

		//Берем настройки логирования из конфигурационного файла
		if(isset($config['db_error_log']))      { $this->_error_log = $config['db_error_log']; }
		if(isset($config['db_query_log']))      { $this->_query_log = $config['db_query_log']; }
		if(isset($config['db_error_log_file'])) { $this->_error_log_file = $config['db_error_log_file']; }
		//Настройка основных параметров PDO
		$this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
		$this->setAttribute(PDO::ATTR_STATEMENT_CLASS,array('QueryStatement',array($this)));

  		//Подключаем статическую версию класса
		new sQuery($this);
	}

	/**
	* @Method: QueryStatement PREPARE($sql,$options=array(),$prefix=NULL)
	*	@param: string $sql - SQL запрос к базе данных (также можно использовать объект класса SQL
	*	@param: array $options - опции для настройки PDO через метод (по-умолчанию: array())
	*	@param: string/NULL $prefix - устанавливает новый префикс
	*	@return: object QueryStatement - создает соответсвующий объект
	* Выполняет метод PDO::PREPARE (http://ru.php.net/manual/ru/pdo.prepare.php)
	*/
	public function PREPARE($sql,$options=array(),$prefix=NULL)
	{
		$sql=$this->_parcer($sql,$prefix);
		return parent::prepare($sql,$options);
	}

 	/**
	* @Method: false/Integer EXEC($sql,$prefix=NULL)
	*	@param: string $sql - SQL запрос к базе данных (также можно использовать объект класса SQL
	*	@param: string/NULL $prefix - устанавливает новый префикс
	*	@return: integer/false - число или false - если не выполнилось
	* Выполняет метод PDO::EXEC (http://ru.php.net/manual/ru/pdo.exec.php)
	*/
	public function EXEC($sql,$prefix=NULL)
	{
		$sql  = $this->_parcer($sql,$prefix);
		try {
			$time   = microtime(true);
			$result = parent::EXEC($sql);
			if($this->_query_log) {
				self::$query[$sql]['time']   = microtime(true)-$time;
				self::$query[$sql]['sql']    = $sql;
				self::$query[$sql]['rows']   = $result;
				self::$query[$key]['result'] = ($result > 0);
			}
			return $result;
		} catch(PDOException $e) { return $this->_error($e); }
	}
  	/**
	* @Method: QueryStatement QUERY($sql,$prefix=NULL,...)
	*	@param: string $sql - SQL запрос к базе данных (также можно использовать объект класса SQL
	*	@param: string/NULL $prefix - устанавливает новый префикс
	*	@param: mixed ... - дополнительные параметры PDO::QUERY
	*	@return: QueryStatement - объект
	* Выполняет метод PDO::QUERY (http://ru.php.net/manual/ru/pdo.query.php)
	*/
	public function QUERY($sql,$prefix=NULL)
	{
		$sql  = $this->_parcer($sql,$prefix);
		$p = func_get_args();
		$time = microtime(true);
		try {
		
			if(isset($p[2],$p[3],$p[4])) $result = parent::QUERY($sql,$p[2],$p[3],$p[4]);
 			elseif(isset($p[2],$p[3]))   $result = parent::QUERY($sql,$p[2],$p[3]);
			elseif(isset($p[2]))     	 $result = parent::QUERY($sql,$p[2]);
			else                         $result = parent::QUERY($sql);
			if($this->_query_log) {
				query::$query[$sql]['timed']       = microtime(true)-$time;
				query::$query[$sql]['queryString'] = $sql;
				query::$query[$sql]['sql']         = $sql;
				query::$query[$sql]['rows']        = $result->rowcount();
				query::$query[$sql]['column']      = $result->columncount();
			}
		} catch(PDOException $e) { return $this->_error($e); }

		return $result;
	}


   	/**
	* @Method: private  string _parcer($sql,$prefix)
	*	@param: string $sql - SQL запрос к базе данных (также можно использовать объект класса SQL
	*	@param: string/NULL $prefix - устанавливает новый префикс
	*	@return: string - специально спарсированный запрос (замена спец символов)
	* Заменяет специальные сиволы:
	* 	?? - заменяется на префиксный литерал
	*	[param] - заменяет на param = ?
	*/
	private function _parcer($sql,$prefix)
	{
		//Адаптация под мои классы SQL
		if($sql instanceof SQL) { $sql = $sql->sql; }
		if(is_null($prefix)) { $prefix=$this->_prefix; }
		$sql=str_replace('??',$prefix,$sql);
		preg_match_all("|\[(.*?)\]|im",$sql,$obj);
		foreach($obj[1] as $key=>$value) {
			$sql=str_replace($obj[0][$key],$value.'=?',$sql);
		}
		return $sql;
	}
	
	private function _error($e)
	{
		//Если возника ошибка записываем в файл лог
		if(!is_null($this->_error_log_file)) {
			$data = date("d.m.Y H:i:s").' :: ('.$e->getCode().')'.$e->getMessage();
			file_put_contents($this->_error_log_file,$data,FILE_APPEND);
		}	
				
		if($this->_error_log) {
			self::$error[$sql]['exception'] = $e;
			self::$error[$sql]['sql']       = $sql;			
		} else { throw new Exception($e->getMessage(),$e->getCode(),$e);	}
		return false; 
	}

}

///Статическая версия Query
class sQuery
{
	public static $db;
	public function __construct(query $db)
	{
		self::$db=$db;
	}

	//http://ru.php.net/manual/ru/pdo.errorcode.php
	public static function errorCode()
	{
		return self::$db->errorCode();
	}

	//http://ru.php.net/manual/ru/pdo.errorinfo.php
	public static function errorInfo()
	{
		return self::$db->errorInfo();
	}

	//http://ru.php.net/manual/ru/pdo.prepare.php
	public static function prepare($sql,$prefix=NULL)
	{
		return self::$db->prepare($sql,array(),$prefix);
	}

 	//http://ru.php.net/manual/ru/pdo.exec.php
	public static function EXEC($sql,$prefix=NULL)
	{
		return self::$db->EXEC($sql,$prefix);
	}

	//http://ru.php.net/manual/ru/pdo.query.php
	public static function query($sql,$prefix=NULL)
	{
		$p = func_get_args();
		if(isset($p[2],$p[3],$p[4])) $result = self::$db->query($sql,$prefix,$p[2],$p[3],$p[4]);
 		elseif(isset($p[2],$p[3]))   $result = self::$db->query($sql,$prefix,$p[2],$p[3]);
		elseif(isset($p[2]))     	 $result = self::$db->query($sql,$prefix,$p[2]);
		else                         $result = self::$db->query($sql,$prefix);
		return $result;
	}

	public static function lastInsertId($name=NULL)
	{
		return self::$db->lastInsertId($name);
	}

	public static function quote($string,$type=PDO::PARAM_STR)
	{
		return self::$db->quote($string,$type);
	}

	public static function FOUND_ROWS()
	{
		$q=self::query("SELECT FOUND_ROWS()");
		return $q->FetchColumn();
	}

	public static function pager($sql,$offset,$limit,$params=array())
	{
		if(class_exists("sql")) {
			if($sql instanceof sql_select) $sql= $sql->sql;
			if(strpos($sql,'SQL_CALC_FOUND_ROWS') === false) $sql=str_replace("SELECT","SELECT SQL_CALC_FOUND_ROWS",$sql);
			$sql=sql::QUERY($sql,'sql_select')->limit($limit,$offset);
			$result['data']=$result[0]=self::prepare($sql)->execute($params);
			$list=ceil(self::FOUND_ROWS()/$limit);
			for($i=0;$i<$list;$i++) $result['list'][$i+1]=$result[1][$i+1]=$i*$limit;
			return $result;
		} else { throw new exception("Будет работать только с классом SQL"); }
	}

   /* Методы работы с транзакциями */

   //http://ru.php.net/manual/ru/pdo.begintransaction.php
	public static function beginTransaction()
	{
		return self::$db->beginTransaction();
	}

	public static function commit()
	{
		return self::$db->commit();
	}

	public static function rollBack()
	{
		return self::$db->rollBack();
	}

	public static function inTransaction()
	{
		return self::$db->inTransaction();
	}

}

class QueryStatement extends PDOStatement
{
  	private $_query;           //Доступ к методам Query
	private $_params=array();  //Параметры для плейсхолдеров
	private $_sql;             //SQL-запросы
	private $_error_log=true;
	private $_query_log=true;
	public  $error = false;
	public  $result;
	private function __construct($q)
	{
		$this->_sql       = $this->queryString;
        $this->_query     = $q;
        $this->_error_log = $q->_error_log;
        $this->_query_log = $q->_query_log;
	}


	public function execute($params=array())
	{
		if(!is_array($params)) { throw new Exception("PDO::EXECUTE The parameter is incorrect"); }
		foreach($params as $key=>$value)
		{
				if(is_numeric($key)) { $key++; }

			    if(is_numeric($value)) { $this->bindValue($key,$value,PDO::PARAM_INT);  }
			elseif(is_bool($value)   ) { $this->bindValue($key,$value,PDO::PARAM_BOOL); }
			elseif(is_null($value)   ) { $this->bindValue($key,$value,PDO::PARAM_NULL); }
			                  	  else { $this->bindValue($key,$value,PDO::PARAM_STR);  }
		}

		//Преобразование конструкции скл запросов в привычный текст
		foreach($this->_params as $key=>$value) {
			switch($value[1]) {
			 case PDO::PARAM_STR:   $value[0] = "'{$value[0]}'"; break;
			 case PDO::PARAM_BOOL:  $value[0] = $value[0]?'TRUE':'FALSE'; break;
			 case PDO::PARAM_NULL:  $value[0] = 'NULL'; break;
			 case PDO::PARAM_LOB:   $value[0] = 'object_lob'; break;
			 case PDO::PARAM_INT:   $value[0] = intval($value[0]); break;
			}
			
			if(is_numeric($key)) {
				$pos       = strpos($this->_sql,'?');
				$len       = strlen($this->_sql);
				$subtext   = substr($this->_sql,$pos+1,$len  );
				$this->_sql  = substr($this->_sql,0,$pos+1);
				$this->_sql  = str_replace('?',$value[0],$this->_sql);
				$this->_sql .= $subtext;
			} else { $this->_sql = str_replace(':'.$key,$value[0],$this->_sql); }
		}
		//Система логирования из запуска стандарного execute
		$this->error = false;
		try {
			$time = microtime(true);
			$this->result[__FUNCTION__]=parent::execute(); //PDO::execute();
			if($this->_query_log) {
				query::$query[$this->_sql]['timed']        = microtime(true)-$time;
				query::$query[$this->_sql]['sql']         = $this->_sql;
				query::$query[$this->_sql]['queryString'] = $this->queryString;
				query::$query[$this->_sql]['rows']        = $this->rowcount();
				query::$query[$this->_sql]['column']      = $this->columncount();
				query::$query[$this->_sql]['params']      = $this->_params;
			}
			return $this;
		} catch(PDOException $e) { 
			$this->error=true; 
			if(!is_null($this->_error_log_file)) {
				$data = date("d.m.Y H:i:s").' :: ('.$e->getCode().')'.$e->getMessage();
				file_put_contents($this->_error_log_file,$data,FILE_APPEND);
			}				
			if($this->_error_log) {
				query::$error[$this->id]['exception']   = $e;
				query::$query[$this->id]['queryString'] = $this->queryString;
				query::$error[$this->id]['sql']         = $this->_sql;
				query::$error[$this->id]['time']        = time();	
			} else { throw new Exception($e->getMessage(),$e->getCode()); }
			return $this;
		}
	}

	public function BindValue($param,$value,$type=PDO::PARAM_STR)
	{
		$this->result[__FUNCTION__][]=parent::BindValue($param,$value,$type);
		$this->_params[$param]=array($value,$type);
		return $this;
	}

	public function BindParam($param,&$value,$type=PDO::PARAM_STR, $maxlen = NULL, $driverdata = NULL)
	{
		$this->result[__FUNCTION__][]=parent::bindParam($param,$value,$type,$maxlen,$driverdata);
		$this->_params[$param]=array(&$value,$type);
		return $this;
	}

	public function BindColumn($column,&$param,$type = NULL, $maxlen = NULL, $driverdata = NULL)
	{

		$this->result[__FUNCTION__][]=parent::bindColumn($column,$param,$type,$maxlen,$driverdata);
		return $this;
	}

	public function SetFetchMode($mode,$params=NULL,$nextargs=NULL)
	{
		$this->result[__FUNCTION__][]=parent::setFetchMode($mode,$params,$nextargs); 
		
		if($this->_query_log) {
			foreach($this as $key=>$value){ query::$query[$this->_sql]['result']=$value; }
		}
		return $this;
	}

	public function SetAttribute($attr,$value)
	{
		$this->result[__FUNCTION__][]=parent::setAttribute($attr,$value);
		return $this;
	}

	public function CloseCursor()
	{
		$this->result[__FUNCTION__][]=closeCursor();
		return $this;
	}

	public function NextRowset()
	{
		$this->result[__FUNCTION__][]=nextRowset();
		return $this;
	}

	public function FetchALL($style=PDO::FETCH_ASSOC,$classname=NULL,$ctor_args=array())
	{
		if($this->error) { return NULL; exit; }
		if(is_null($classname) && (count($ctor_args) ==0)) {
			$result=parent::FetchALL($style);
		} else {
			$result=parent::FetchALL($style,$classname,$ctor_args);
		}
		if($this->_query_log) query::$query[$this->_sql]['result']=$result[0];
		return $result;
	}
	

	public function Fetch($style=PDO::FETCH_ASSOC,$cursor_orientation=PDO::FETCH_ORI_NEXT,$cursor_offset=0)
	{
		if($this->error) { return NULL; exit; }

		$result=parent::Fetch($style,$cursor_orientation,$cursor_offset);
  
		if($this->_query_log) query::$query[$this->_sql]['result']=$result;
		return $result;
	}
	
	public function FetchColumn($column=0)
	{
		if($this->error) { return NULL; exit; }
		$result=parent::fetchColumn($column);
		if($this->_query_log) query::$query[$this->_sql]['result']=$result;
		return $result;
	}

	public function FetchObject($class_name = NULL, $ctor_args = NULL)
	{
		if($this->error) { return NULL; exit; }
		$result=parent::fetchObject($class_name,$ctor_args); 
		if($this->_query_log) query::$query[$this->_sql]['result']=$result;
		return $result;
	}
}
?>